package it.polimi.protopeer.simulation.manager;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;




import it.polimi.protopeer.simulation.manager.MachineSSHAddress;

public class Controller {
	private static Logger log = Logger.getLogger(Controller.class);
	private static List<MachineSSHAddress> machines = new ArrayList<MachineSSHAddress>();
	private static String remotePath;
	private static String resultPath;
	private static String localjar;
	private static String cmdCommand; 
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		PropertyConfigurator.configure("conf/log4j.properties");
		parseHost();
		Controller manager=new Controller();
		manager.performSimulation();
	}
	
	/**
	 * ricavo i parametri dal file xml
	*/ 
	public static void parseHost(){
		
		try{
				SAXParserFactory factory = SAXParserFactory.newInstance();
	  	      	SAXParser saxParser = factory.newSAXParser();
	  	      	DefaultHandler handler = new DefaultHandler() {
	  	      	boolean buser = false;
		        boolean bpass = false;
		        boolean baddr = false;
		        boolean bport = false;
		        boolean bhost = false;
		        boolean bpkey = false;
		        String user="";
		        String pass="";
		        String address="";
		        String pkey="";
		        int port=0;
	
		        public void startElement(String uri, String localName,
		            String qName, Attributes attributes)
		            throws SAXException {
		        	
		          System.out.println("Start Element :" + qName);
	
		          if (qName.equalsIgnoreCase("username")) {
		        	  buser = true;
		          }
	
		          if (qName.equalsIgnoreCase("password")) {
		        	  bpass = true;
		          }
	
		          if (qName.equalsIgnoreCase("address")) {
		        	  baddr = true;
		          }
	
		          if (qName.equalsIgnoreCase("port")) {
		        	  bport = true;
		          }
		          
		          if (qName.equalsIgnoreCase("host")) {
		        	  bhost = true;
		          }
		          
		          if (qName.equalsIgnoreCase("pkey")) {
		        	  bpkey = true;
		          }
		          
		          
		        }
	
		        public void endElement(String uri, String localName,
		                String qName)
		                throws SAXException {
	
		              System.out.println("End Element :" + qName);
		              if (bhost && !user.equals("") && port!=0 && !address.equals("") && (!pass.equals("") || !pkey.equals(""))) {
		            	  if(pass.equals(""))
		            		  machines.add(new MachineSSHAddress(user, address, pkey, port));
		            	  else
		            		  machines.add(new MachineSSHAddress(user, address, port, pass));
			        	  bhost = false;
			        	  user="";
					      pass="";
					      address="";
					      port=0;
			          }
		              
	
		        }
	
		        public void characters(char ch[], int start, int length)
		            throws SAXException {
	
		          System.out.println(new String(ch, start, length));
		          	
		          if (buser) {
		        	  user=new String(ch, start, length);
		        	  buser = false;
		          }
	
		          if (bpass) {
		        	  pass=new String(ch, start, length);
		              bpass = false;
		           }
	
		          if (baddr) {
		              address=new String(ch, start, length);
		              baddr = false;
		           }
	
		          if (bport) {
		        	  port=Integer.parseInt(new String(ch, start, length));
		              bport = false;
		           }
		          
		          if (bpkey) {
		        	  pkey=new String(ch,start,length);
		        	  bpkey=false;
		          }
		          
	
		        }
  	      	};
  	      	File file = new File("./nodes.xml");
  	      	InputStream inputStream= new FileInputStream(file);
  	      	Reader reader = new InputStreamReader(inputStream,"UTF-8");

  	      	InputSource is = new InputSource(reader);
  	      	is.setEncoding("UTF-8");

  	      	saxParser.parse(is, handler);
		}catch(Exception e){
			log.error(e);
			e.printStackTrace();
		}
	}

	public void generateCommand(String addresses){
		cmdCommand="";
//		cmdCommandApp="";
		cmdCommand="java -jar " + remotePath;
		cmdCommand = " -NEIGHBORS " + addresses + " ";
//		
//		if(algorithm.equals("Null"))
//			cmdCommandApp += "NullOptimizerStrategy";
//		else if(algorithm.equals("SAT"))
//			cmdCommandApp += "SATOptimizerStrategy";
//		else if(algorithm.equals("OCBR"))
//			cmdCommandApp += "OCBROptimizerStrategy";
		
//		cmdCommandApp += " -" + LocalManager.OPTION_ALGORITHM_PARAMETERS + " " + algorithm_parameters +
//		" -" + LocalManager.OPTION_CLASSES_PER_SUBSCRIPTION + " " + classes_subscription +
//		" -" + LocalManager.OPTION_DELAY_MESSAGES_MEAN + " " + delay_messages_mean +
//		" -" + LocalManager.OPTION_DELAY_MESSAGES_STDEV + " " + delay_messages_stdev +
//		" -" + LocalManager.OPTION_DURATION + " " + duration +
//		" -" + LocalManager.OPTION_MESSAGE_CLASSES + " " + message_classes +
//		" -" + LocalManager.OPTION_MESSAGES_CLASSES_DISTRIBUTION + " " + message_classes_distribution +
//		" -" + LocalManager.OPTION_NUM_COMPONENT_SUBSCRIPTIONS + " " + num_component_subscription +
//		" -" + LocalManager.OPTION_NUM_COMPONENTS + " " + num_components +
//		" -" + LocalManager.OPTION_SEED + " " + seed +
//		" -" + LocalManager.OPTION_SUBSCRIPTIONS_CLASSES_DISTRIBUTION + " " + subscriptions_classes_distribution+
//		" -" + LocalManager.OPTION_STARTUP_NEIGHBORHOOD_TIMESTAMP + " " + ((int) (System.currentTimeMillis()/1000) + 9) +
//		" -" + LocalManager.OPTION_STARTUP_TRAFFIC_TIMESTAMP + " " + ((int) (System.currentTimeMillis()/1000) + 17) +
//		" -" + LocalManager.OPTION_SIMULID + " " +  ((int) (System.currentTimeMillis()/1000));
	}
	
	/**
	 * Perform all operation to perform a simulation
	 */
	public void performSimulation() {
		log.info("*** STARTING SIMULATION ***");
		System.out.println("*** STARTING SIMULATION ***");
		
		// updating jars
		updateJars();
		
		// creating initial topology
//		nodes = new ArrayList<Node>();
//		for (MachineSSHAddress machine:machines) {
//			for (int i=0; i<machine.getNumBrokers(); i++) {
//				nodes.add(new Node(machine,i));
//			}
//		}
//		links = (new ScaleFreeTopology(nodes,rand)).createTopology();
		
		// run simulation on remote nodes
		startRemotePeers();
		log.info("*** SIMULATION COMPLETED ***");
//		System.out.println("*** SIMULATION COMPLETED ***");
//		chart=new ChartCreator(resultPath,algorithm);
//		System.out.println("*** INIZIO GENERAZIONE GRAFICI ***");
//		chart.createChart("result.csv");
//		System.out.println("*** TERMINE GENERAZIONE GRAFICI ***");
	}
	
	/**
	 * Initialize all nodes and then connect them
	 */
	public void startRemotePeers() {
		log.info("Running simulation on remote peers...");
		System.out.println("Running simulation on remote peers...");
		for (MachineSSHAddress machine:machines) {
			//Assegno la porta da utilizzare al nodo
//			int port=10000+node.getID();
			SSHConnection connection=machine.getConnection();
//			String initialNeighbors = initialNeighborsURLs(node);				
//			String cmdApp;
//			if(initialNeighbors.length()>0){
//				cmdApp=" -" + LocalManager.OPTION_INITIAL_NEIGHBORS + " " + initialNeighbors;
//			}else{
//				cmdApp="";
//			}

			connection.execute(cmdCommand+machine.getUsername()+"/protopeer.jar");//+" >> node" + node.getID() + ".log 2>&1");
		}
		long time=System.nanoTime();
		long time2;
		System.out.println("Avvio dei nodi eseguito");//<-----------------------
		//Lascio passare 5000 millisecondi
//		try {
//			Thread.sleep(5000);
//		} catch (InterruptedException e) {}
//		System.out.println("AVVIO RESULTCOLLECTOR");
//		resultsCollector = new ResultsCollector(time);
//		resultsCollector.start();
//		int i=0;
//		
//		ArrayList<String> as=new ArrayList<String>();
//		for (String str:aggregatedStatistics) {
//			as.add(str);
//		}
//		
//		do{
//			i++;
//			try {
//				Thread.sleep(1000);
//			} catch (InterruptedException e) {}
//			
//			collectedData.put("time",i);
//			//Elimino dal set di risultati i parametri ininfluenti
//			collectedData.remove(LocalManager.OPTION_PORT);
//			collectedData.remove(LocalManager.OPTION_DURATION);
//			collectedData.remove(LocalManager.OPTION_SEED);
//			collectedData.remove(LocalManager.OPTION_MESSAGE_CLASSES);
//			collectedData.remove(LocalManager.OPTION_NUM_COMPONENTS);
//			collectedData.remove(LocalManager.OPTION_CLASSES_PER_SUBSCRIPTION);
//			collectedData.remove(LocalManager.OPTION_NUM_COMPONENT_SUBSCRIPTIONS);
//			collectedData.remove(LocalManager.OPTION_STARTUP_TRAFFIC_TIMESTAMP);
//			collectedData.remove(LocalManager.OPTION_STARTUP_NEIGHBORHOOD_TIMESTAMP);
//			collectedData.remove(LocalManager.OPTION_DELAY_MESSAGES_MEAN);
//			collectedData.remove(LocalManager.OPTION_DELAY_MESSAGES_STDEV);
//			
//			//Genero i risultati della simulazione in un file CVS e su console
//			ResultsAnalyzer results = new ResultsAnalyzer(collectedData);
//			
//			
//			results.writeResultsToFileCSV(as, resultPath+algorithm+"_result.csv");
//			results.writeResultsToConsole(as);
////			try {
////				//aspetto che il tread termini la raccolta dati
////				resultsCollector.join();
////			} catch (InterruptedException e) {
////				e.printStackTrace();
////			}
//			time2=System.nanoTime()-time;
//		}while(TimeUnit.NANOSECONDS.toSeconds(time2)<simulationTime);
//			
			
		//Chiudo tutte le sessioni aperte
		for (MachineSSHAddress machine:machines) {
			SSHConnection connection=machine.getConnection();
			connection.disconnect();
		}
		log.info("SIMULATION COMPLETED!");
		System.out.println("SIMULATION COMPLETED!");
		
	}
	
	/**
	 * Update the file jar on the remote host
	 */
	public void updateJars() {
		log.info("Updating jars on remote machines...");
		System.out.println("Updating jars on remote machines...");
		
		
		for (MachineSSHAddress machine:machines) {
			SSHConnection connection=machine.getConnection();
			connection.putFile(localjar,remotePath+machine.getUsername()+"/redsoptimizer.jar");
		}
	}
	
	/**
	 * Initialize the neighbors
	 * @param node node from which calculate neighbors
	 * @return a string that contains neighbours
	 */
//	private String initialNeighborsURLs(Node node) {
//		List<Node> neighbors = new ArrayList<Node>();
//		
//		for (Edge link:links) {
//			if (link.getNode1().equals(node)) {
//				neighbors.add(link.getNode2());
//			} //else if (l.getNode2().equals(node)) {
//			 //	neighbors.add(l.getNode1());
//			//}
//		}
//		
//		String s="";
//		for (Node n:neighbors) {
//			int port=10000 + n.getID().intValue();
//			s += ",reds-tcp:" + n.getMachineSSHAddress().getHost() + ":" + port;
//		}
//		if (s.length() > 0) {
//			s=s.substring(1);
//		}
//		
//		return s;
//	}
	
	/**
	 * Download the result file from each node and then it makes a sum of results
	 * @return a map of sums of results
	 */
	private Map<String,Object> getRemoteResults() {
		System.out.println("GETRESULT DATA");
		List<Map<String,Object>> data = new ArrayList<Map<String,Object>>();
//		for (Node node:nodes) {
//			SSHConnection connection=node.getMachineSSHAddress().getConnection();
//			connection.getFile(remotePath+node.getMachineSSHAddress().getUsername()+"/"+node.getMachineSSHAddress().getHost() + "." + (10000 + node.getID().intValue()) + ".txt",resultPath+node.getMachineSSHAddress().getHost() + "." + (10000 + node.getID().intValue()) + ".txt");
//			data.add(readData(resultPath+node.getMachineSSHAddress().getHost() + "." + (10000 + node.getID().intValue()) + ".txt"));
//		}
		
		ArrayList<String> as=new ArrayList<String>();
//		for (String str:aggregatedStatistics) {
//			as.add(str);
//		}
		return sumData(data, as);
	}
	
	
	/**
	 * It's used to aggregate all results from hosts
	 * @param data a map of result
	 * @param what list of parameters used to aggregate the results
	 * @return a map of aggregation data
	 */
	private Map<String,Object> sumData(List<Map<String,Object>> data, List<String> what) {
		Map<String,Object> aggregatedData = new HashMap<String,Object>();
		
		for (Map<String,Object> dat:data) {
			for (String key:dat.keySet()) {
				if (!what.contains(key) || !(dat.get(key) instanceof Integer || dat.get(key) instanceof Double)) {
					aggregatedData.put(key, dat.get(key));
				} else if (dat.get(key) instanceof Integer) {
					if (aggregatedData.get(key) == null) {
						aggregatedData.put(key, dat.get(key));
					} else {
						aggregatedData.put(key, (Integer) aggregatedData.get(key) + (Integer) dat.get(key)); 
					}
				} else if (dat.get(key) instanceof Double) {
					if (aggregatedData.get(key) == null) {
						aggregatedData.put(key, dat.get(key));
					} else {
						aggregatedData.put(key, (Double) aggregatedData.get(key) + (Double) dat.get(key)); 
					}
				}
			}
		}
		return aggregatedData;
	}
	
	/**
	 * Reads the file and extracts the data
	 * @param fileName file to read
	 * @return map of data read from file
	 */
	private Map<String,Object> readData(String fileName) {
		Map<String,Object> map = new HashMap<String,Object>();
		FileReader fileReader=null;
		try {
			fileReader = new FileReader(fileName);
		} catch (FileNotFoundException e) {
			log.error("Unable to open file: " + fileName);
			System.err.println("Unable to open file: " + fileName);
			return map;
		}
	    BufferedReader reader = new BufferedReader(fileReader);

	    try {
	      
	    	String line;
	    	do {
	    		line = reader.readLine();
	    		if (line != null && line.length()>0 && line.contains("=")) {
	    			String[] splitLine = line.split("=");
	    			if ((splitLine.length<2) || splitLine[1].length()==0 || splitLine[1].substring(1,1).equals("\""))  {
	    				continue;
	    			}
	    			
	    			try {
	    				map.put(splitLine[0], Double.parseDouble(splitLine[1]));
	    			} catch (NumberFormatException e) {
	    				continue;
	    			}
	    		}
	    		
	    	} while (line != null);
	    	
	      reader.close();
	      fileReader.close();

	    } catch (IOException e) {
	      e.printStackTrace();
	    }
	    
	    return map;
	}
	/**
	 * Nested class
	 * This thread handles all data transfer and aggregation.
	 * @author ricky
	 *
	 */
	private class ResultsCollector extends Thread {
		long time;
		public ResultsCollector(long time) {
			this.time=time;
			this.setDaemon(true);
		}
		
		public void run() {
			long t=System.nanoTime()-time;
			
//			while (TimeUnit.NANOSECONDS.toSeconds(t)<simulationTime) {
//				try {
//					Thread.sleep(5000);
//				} catch (InterruptedException e) {
//				}
//				collectedData = getRemoteResults();
//				t=System.nanoTime()-time;
//			}
		}
	}
}
